#include "wal.h"
#include "absl/crc/crc32c.h"
#include <algorithm>
#include <cstdint>
#include <string>

namespace wal {

void WAL::write(const wal::bytes &data) {
  std::unique_lock<std::shared_mutex> lock(mu->mtx);

  int dataLen = data.size();
  size_t new_size = file_size + ((dataLen + maxPayloadSize - 1) / maxPayloadSize) * recordSize;

  // Resize file if needed
  if (new_size > mmap.size()) {
		std::error_code error;
    mmap.sync(error);
		if (error) {
      throw std::runtime_error("Failed to remap file: " + error.message());
		}
    mmap.unmap();

    // Extend the file
    std::ofstream file(filename, std::ios::binary | std::ios::app);
    file.seekp(new_size - 1);
    file.put('\0');
    file.close();

    // Remap the file
    mmap = mio::make_mmap_sink(mmap.data(), 0, mio::map_entire_file, error);
    if (error) {
      throw std::runtime_error("Failed to remap file: " + error.message());
    }
  }

  int offset = 0;
  if (dataLen == 0) {
    RecordType rt = RecordTypeZero;
    record[0] = static_cast<byte>(rt);
    int payloadSize = 0;
    record[1] = static_cast<byte>(payloadSize >> 8);
    record[2] = static_cast<byte>(payloadSize & 0xff);
		std::string s;
		s.push_back(record[recordHeaderSize]);
		absl::crc32c_t crc_value = absl::ComputeCrc32c(s);
		uint32_t crc = static_cast<uint32_t>(crc_value);
    record[3] = static_cast<byte>(crc >> 24);
    record[4] = static_cast<byte>((crc >> 16) & 0xff);
    record[5] = static_cast<byte>((crc >> 8) & 0xff);
    record[6] = static_cast<byte>(crc & 0xff);

    std::copy(record.begin(), record.begin() + recordSize,
              mmap.begin() + file_size);
    file_size += recordSize;
    return;
  }

  while (offset < dataLen) {
    int remainingBytes = dataLen - offset;
    int payloadSize = min(maxPayloadSize, remainingBytes);

    RecordType rt;
    if (offset == 0) {
      rt = (payloadSize == dataLen) ? RecordTypeFull : RecordTypeFirst;
    } else if (offset + payloadSize == dataLen) {
      rt = RecordTypeLast;
    } else {
      rt = RecordTypeMiddle;
    }

    record[0] = static_cast<byte>(rt);
    record[1] = static_cast<byte>(payloadSize >> 8);
    record[2] = static_cast<byte>(payloadSize & 0xff);

    std::copy(data.begin() + offset, data.begin() + offset + payloadSize,
              record.begin() + recordHeaderSize);
		std::string s;
		s.push_back(record[recordHeaderSize]);
		absl::crc32c_t crc_value = absl::ComputeCrc32c(s);
		uint32_t crc = static_cast<uint32_t>(crc_value);
    record[3] = static_cast<byte>(crc >> 24);
    record[4] = static_cast<byte>((crc >> 16) & 0xff);
    record[5] = static_cast<byte>((crc >> 8) & 0xff);
    record[6] = static_cast<byte>(crc & 0xff);

    std::copy(record.begin(), record.begin() + recordSize,
              mmap.begin() + file_size);

    offset += payloadSize;
    file_size += recordSize;
  }
 }

std::optional<bytes> WAL::WalIterator::next() {
	  std::shared_lock<std::shared_mutex> lock(mu->mtx);

  if (offset >= mmap.size()) {
    return std::nullopt;
  }

  bytes fullData;

  while (offset < mmap.size()) {
    std::copy(mmap.begin() + offset, mmap.begin() + offset + recordSize,
              record.begin());
    offset += recordSize;

    RecordType recordType = static_cast<RecordType>(record[0]);
    uint16_t payloadSize = 0;
    payloadSize |= static_cast<uint16_t>(record[1]) << 8;
    payloadSize |= static_cast<uint16_t>(record[2]);

    uint32_t crc = 0;
    crc |= static_cast<uint32_t>(record[3]) << 24;
    crc |= static_cast<uint32_t>(record[4]) << 16;
    crc |= static_cast<uint32_t>(record[5]) << 8;
    crc |= static_cast<uint32_t>(record[6]);
		std::string s;
		s.push_back(record[recordHeaderSize]);
    auto dcrc_value = absl::ComputeCrc32c(s);
		uint32_t dcrc = static_cast<uint32_t>(dcrc_value);
    if (crc != dcrc) {
      throw std::runtime_error("crc check failed");
    }

    bytes payload(record.begin() + recordHeaderSize,
                 record.begin() + recordHeaderSize + payloadSize);

    switch (recordType) {
    case RecordTypeZero:
      return bytes();
    case RecordTypeFull:
      return payload;
    case RecordTypeFirst:
      fullData = payload;
      break;
    case RecordTypeMiddle:
      fullData.insert(fullData.end(), payload.begin(), payload.end());
      break;
    case RecordTypeLast:
      fullData.insert(fullData.end(), payload.begin(), payload.end());
      return fullData;
    }
  }
  return std::nullopt;
}

void wal::WAL::sync() {
  std::unique_lock<std::shared_mutex> lock(mu->mtx);
	std::error_code error;
  mmap.sync(error);
	if (error) {
		throw error;
	}
}

wal::WAL::WalIterator wal::WAL::iter() {
  return WalIterator(filename.data(), this->mu);
}
} // namespace wal
